-- vim: set autoindent noexpandtab tabstop=4 shiftwidth=4 softtabstop=-1:
--[[
    This file is part of mtm mod.

    mtm mod is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    mtm mod is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with mtm mod.  If not, see <https://www.gnu.org/licenses/>.
--]]
---
-- The purpose of this mod was orignaly to aid in creating and mantaging
-- serious servers and to provide simple and easy storage for mods that was
-- both simple and fast.
--
-- Storage uses my lua package dbtable which allows access to a PostgreSQL
-- or sqlite3 database by creating siple key/value tables. Compression and
-- serialization is transparent allowing for the seemless storage of tables
-- as well as boolean, integer, and string data types.
--
-- The mtm must be a trusted mod for it to work. I currently have two mods
-- that depend on this mod to work, beds, and auth. The simplified interface
-- makes the code in those two mods extremely simple. The data is accessed
-- exactly like a table.
--
-- @author James Dornan james _at_ catch22.com
-- @copyright James Dornan
-- @license GPLv3
-- @module mtm

-- ldoc -f discount -p 'Minetest Beds Mod' -d <output directoru> -a init.lua

--- Maine mtm class table
-- @field name of the mod
-- @field worldmt a table of the key/values in world.mt
-- @field reserved_names a table of forbidden storage names
-- @field path string
local mtm = {
	name = 'mtm',
	worldmt = {},
	reserved_names = {
		world_log = true
	},
}

--- Read in the world.mt file and place the contents into a table
-- @return table
function mtm.get_worldmt()
	if #mtm.worldmt > 0 then
		return mtm.worldmt
	end

	local fileName = minetest.get_worldpath() .. '/world.mt'
	local file = assert(
		io.open(fileName, 'r'),
		'Error loading file : ' .. fileName
	)

	local data = {}

	for line in file:lines() do
		local key, value = line:match('^([%w|_]+)%s-=%s-(.+)$')

		key   = string.gsub(key, '^%s*(.-)%s*$', '%1')
		value = string.gsub(value, '^%s*(.-)%s*$', '%1')

		if key and value ~= nil then
			if tonumber(value) then
			  value = tonumber(value)
			elseif value == 'true' then
			  value = true
			elseif value == 'false' then
			  value = false
			end

			if tonumber(key) then
				key = tonumber(key)
			end

			data[key] = value
		end
	end

	file:close()

	assert(next(data) ~= nil, fileName .. ' appears to be empty.')

	mtm.worldmt = data

	return data
end

--- Get the backend used in the world.mt file
-- @return string
function mtm.get_backend()
	if mtm.worldmt['world_backend'] then
		return mtm.worldmt['world_backend']
	end

	if mtm.worldmt['backend'] == 'postgresql' then
		return mtm.worldmt['backend']
	end

	return 'sqlite3'
end

--- Get connection string. For sqlite3 a file and for PostgreSQL a connection string.
-- @return string
function mtm.get_connect(backend)
	if backend == 'sqlite3' then
		return minetest.get_worldpath() .. '/world.sqlite'
	elseif mtm.worldmt['backend'] == 'postgresql' then
		minetest.log('error', '[mtm] pgsql_connection not set in world.mt.')
		assert(
			mtm.worldmt['pgsql_connection'] ~= nil,
			'pgsql_connection not set in world.mt.'
		)
			return mtm.worldmt['pgsql_connection']
	end
end

--- Return a dbtable object used for simple storage
-- @param[opt] string name of the backend db table, default mod_<modname>
-- @param[opt] string manually set the backend, sqlite3 or PostgreSQL
-- @param[opt] string sqlite3 file name or PostgreSQL connection string
-- @return table dbtable object
function mtm.storage(name, backend, connect)
	if not name then
		name = 'mod_' .. minetest.get_current_modname()
	elseif string.sub(name, 1, 4) == 'mod_' then
		assert(
			name == 'mod_' .. minetest.get_current_modname(),
			'Illegal storage name. Reserved for a specific mod.'
		)
	end

	assert(mtm.reserved_names[name] == nil, 'Reserved storage name used.')

	if not backend then
		backend = mtm.get_backend()
	end

	if not connect then
		connect = mtm.get_connect(backend)
	end

	local opts = {
		db   = backend,
		name = name,
	}

	if backend == 'sqlite3' then
		opts.file = connect
	else
		opts.connection = connect
	end

	minetest.log('info', '[mtm] db \'' .. name .. '\' accessed.')

	return mtm.dbtable.new(opts)
end

--- Add the mtm/packages folder to the lua package path
function mtm.set_package_path()
	package.path = package.path .. ';' .. minetest.get_modpath('mtm') .. '/packages/?.lua'
end

-- Initualize the mod
-- @param table global scope
function mtm.init(_G)
	mtm.get_worldmt()
	mtm.set_package_path()

	assert(mtm.worldmt['backend'] ~= nil, 'backend missing from world.mt')

	mtm.dbtable = assert(
		require('dbtable'),
		'Unable to require dbtable. mtm must be listed as a trusted mod?'
	)

	_G.mtm = mtm

	minetest.log('info', '[mtm] MineTest.Me mod loaded.')
end

mtm.init(_G)
